<!DOCTYPE html>
<html>
  <head>
    <!-- Load JQuery and JQuery-UI -->
    <script type="text/javascript" src="js/jquery-1.4.2.min.js"></script>
    
    <!-- Load Processing.js -->
    <script language="javascript" src="js/processing.js"></script>
    <script language="javascript" src="js/init.js"></script>

    <!-- Load DSP.js -->
    <script src="../dsp.js"></script>

    <style type="text/css">
      body, * {
        font-family: Arial, sans-serif;
      }
      .control {
        padding: 5px;
        border: 1px outset #CCC;
        background-color: #EEE;
        float: left;
        margin-right: 5px;
      }
      .control table td {
        padding: 10px;
        width: 20px;
        color: #999;
        font-size: 12px;
      }
      .control h3 {
        margin: 0;
        padding:0;
        font-size: 12px;
        margin-bottom: 10px;
      }
      .control #debug {
        border: 1px inset #ccc;
        background-color: #FFF;
        font-size: 12px;
        width: 300px;
        padding: 10px;
      }
      .slider {
        margin-bottom: 16px;
        width: 8px;
      }
      
      .ui-slider .ui-slider-handle {
        width: 8px;
        margin-left: 3px;
      }
    </style>
  </head>
  <body>
    <script>
      // Setup shared variables
      var sampleRate = 44100;
      var frameSize = 4096;
      
      var signal = new Float32Array(frameSize/2);
      var lp12;
      var buffer = [];


      // Setup experimental audio out
      var output = new Audio();
      
      if ( typeof output.mozSetup === 'function' ) {
        output.mozSetup(1, sampleRate);
      }

      var changeFilter = function() {
        lp12.set($('#cutoff').slider('option', 'value'), $('#res').slider('option', 'value'));
      }

      function loadedMetaData(event) {
        var audio = document.getElementById('input');
        audio.mozFrameBufferLength = frameSize;
        audio.addEventListener("MozAudioAvailable", audioAvailable, false);
      }

      function audioAvailable(event) {
        signal = DSP.getChannel(DSP.MIX, event.frameBuffer);
        lp12.process(signal);
        output.mozWriteAudio(signal);
      }
    </script>
    
    <script type="application/processing" target="#signal">
      Knob cut;
      Knob res;

      void setup() {
        size(512, 200);

        cut = new Knob("", 60, 6000, 2500, 70, 20, 50, 50);        
        res = new Knob("", 1, 20, 7, 0.3, 20, 100, 50);        

        lp12 = new IIRFilter(DSP.LOWPASS, 22050, 0, sampleRate);

        // mute the input
        document.getElementById('input').muted = true;

        stroke(255, 0, 0);
      }
      
      void draw() {
        background(255);
        lp12.set(cut.value, res.value);
        for (int i = 0; i < width; i+=2) {
          line(i, height/2 - signal[4*i]/2 * 200, i, height/2 + signal[4*i]/2 * 200);
        }
        fill(255, 0, 0);
        text("F", 30, 30);
        text("Q", 75, 30);
        ellipse(52, 52, 40, 40);
        ellipse(102, 52, 40, 40);

        fill(255);
        cut.draw();
        res.draw();
      }

      class Knob {
        float radius;
        float x;
        float y;
        boolean active;
        float mouseYInit;
        float value;
        float min;
        float max;
        float step;
        float percent;
        float angle;
        String label;

        Knob(label, min, max, value, step, radius, x, y) {
          this.radius = radius;
          this.x = x;
          this.y = y;
          this.active = false;
          this.value = value;
          this.min = min;
          this.max = max;
          this.step = step;
          this.label = label;
        }

        void draw() {
          textSize(12);
          ellipseMode(CENTER);
          if (mousePressed) {
            if (this.isOver() && !this.active && !Knob.active) {
              this.active = true;
              this.mouseYInit = mouseY;
              Knob.active = true;
            } else if (this.active) {
              this.value += (this.mouseYInit - mouseY) * this.step;
              this.value = constrain(this.value, min, max);
              this.mouseYInit = mouseY;
            }
          } else {
            this.active = false;
            Knob.active = false;
          }
          
          this.percent =  (this.value - this.min) / (this.max - this.min);
          this.angle = map(this.value, this.min, this.max, radians(-130), radians(130));
          pushMatrix();
            translate(this.x, this.y);
            rotate(this.angle);
            ellipse(0, 0, 2*this.radius, 2*this.radius);
            line(0, 0, 0, -this.radius);
          popMatrix();
          /*
          text(this.label, this.x - this.radius -5, this.y - this.radius - 5);
          text(this.value, this.x+this.radius+5, this.y);
          text(round(this.percent * 100) + "%", this.x+this.radius+5, this.y+20);
          */
        }

        boolean isOver() {
          if (mouseX > this.x - this.radius && 
              mouseX < this.x + this.radius &&
              mouseY > this.y - this.radius &&
              mouseY < this.y + this.radius) {
              return true;
          }
          return false;
        }
      }

    </script>
    
    <h1>Audio Filter</h1>
    <p>Applies a Low pass filter to an audio stream. Remember to <b>Mute</b> the audio to hear the pure filtered sound.</p>
    <p>The low pass filter acts as a gate, only letting frequencies below the cut off value through.</p> 
    <audio id='input' tabindex="0" src="audio/megaman.ogg" controls="true" onloadedmetadata="loadedMetaData(event);" style="width: 512px;"></audio><br>
    <div><canvas id="signal" width="512px" height="200px"></canvas></div>

    <p>The <b>F</b> knob controls the cut off frequency, between 0 and 22050 Hz.</p>
    <p>The <b>Q</b> knob controls the resonance drive.</p>
    <p>A low frequency cutoff and strong resonance can give an acidy or dampened underwater effect.</p>
    <p>(Be careful! Adjust your volume! Certain knob combinations can give off loud undesired schreeching noises!)</p>
    <p>Music: Delv-X: Megaman theme (cover)</p>
  </body>
</html>
